# -*- coding: utf-8 -*-
# Evaluator.py
from EvaluationData import EvaluationData
from EvaluatedAlgorithm import EvaluatedAlgorithm
from BaseDatasetLoader import BaseDatasetLoader


class Evaluator:
    """
    封装推荐算法的评估方法。
    """

    def __init__(self, dataset, rankings):
        """
        初始化 Evaluator 类。

        :param dataset: 原始数据集，通常是一个 Surprise 数据集。
        :param rankings: (itemID, rank) 项目的流行度排名信息，用于后续计算覆盖率等指标。
        """
        # 获取数据集
        ed = EvaluationData(dataset, rankings)
        self.rankings = rankings
        self.dataset = ed

        # 所有待评估的算法
        self.algorithms = []  # ["KNN", "SVD", ...]

    def AddAlgorithm(self, algorithm, name: str):
        """
        添加待评估的推荐算法。

        :param algorithm: 推荐算法实例。
        :param name: 算法名称。
        """
        # 算法封装为 EvaluatedAlgorithm 类
        alg = EvaluatedAlgorithm(algorithm, name)
        # 添加到数组
        self.algorithms.append(alg)

    def Evaluate(self, doTopN: bool):
        """
        评估所有添加的推荐算法的性能。

        :param doTopN: 是否进行 Top-N 推荐评估。
        """
        results = {}
        for algorithm in self.algorithms:
            print("Evaluating ", algorithm.GetName(), "...")
            results[algorithm.GetName()] = algorithm.Evaluate(self.dataset, doTopN)

        # 打印结果
        print("\n")

        if doTopN:
            print("{:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10} {:<10}".format(
                "Algorithm", "RMSE", "MAE", "HR", "CHR", "ARHR", "Coverage", "Diversity", "Novelty"))
            for name, metrics in results.items():
                print("{:<10} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f} {:<10.4f}".format(
                    name, metrics["RMSE"], metrics["MAE"], metrics["HR"], metrics["CHR"], metrics["ARHR"],
                    metrics["Coverage"], metrics["Diversity"], metrics["Novelty"]))
        else:
            print("{:<10} {:<10} {:<10}".format("Algorithm", "RMSE", "MAE"))
            for name, metrics in results.items():
                print("{:<10} {:<10.4f} {:<10.4f}".format(name, metrics["RMSE"], metrics["MAE"]))

        print("\nLegend:\n")
        print("RMSE:        Root Mean Squared Error. Lower values mean better accuracy.")
        print("MAE:         Mean Absolute Error. Lower values mean better accuracy.")
        if doTopN:
            print("HR:        Hit Rate; how often we are able to recommend a left-out rating. Higher is better.")
            print(
                "cHR:        Cumulative Hit Rate; hit rate, confined to ratings above a certain threshold. Higher is better.")
            print(
                "ARHR:        Average Reciprocal Hit Rank; hit rate that takes the ranking into account. Higher is better.")
            print(
                "Coverage: Ratio of users for whom recommendations above a certain threshold exist. Higher is better.")
            print(
                "Diversity: 1-S, where s is the average similarity score between every possible pair of recommendations")
            print("              for a given user. Higher means more diverse.")
            print("Novelty:        Average popularity rank of recommended items. Higher means more novel.")

    def SampleTopNRecs(self, loader: BaseDatasetLoader, userID: int = 85, k: int = 10):
        """
        遍历所有已添加的算法，对用户未评分的项目进行预测。
        输出预测评分最高的前 k 个项目及其评分。

        :param loader: 包含物品信息的对象，通常具有 getItemName 方法。
        :param userID: 用户ID，默认为 85。
        :param k: Top-N 推荐的数量，默认为 10。
        """

        recommendations = []
        for algo in self.algorithms:
            print("\nUsing recommender ", algo.GetName())

            # 获取训练集进行训练
            print("\nBuilding recommendation model...")
            trainSet = self.dataset.GetFullTrainSet()
            algo.GetAlgorithm().fit(trainSet)

            # 获取所有用户未评分的数据
            print("Computing recommendations...")
            testSet = self.dataset.GetAntiTestSetForUser(userID)

            if testSet == []:
                # 冷启动策略, 返回排名最前的 k 个项目
                return loader.get_popularity_itemIDs()[:k]

                # 对未看过的电影进行评分预测
            predictions = algo.GetAlgorithm().test(testSet)
            print("\nWe recommend:")

            # Prediction(uid='6', iid='10080', r_ui=7.8998, est=5, details={'was_impossible': False})
            for userID, itemID, actualRating, estimatedRating, _ in predictions:
                # print(itemID)
                intItemID = int(itemID)
                recommendations.append((intItemID, estimatedRating))

            # 根据评分排序
            recommendations.sort(key=lambda x: x[1], reverse=True)

            # 打印
            # for ratings in recommendations[:k]:
            #     # print(ratings)
            #     print(loader.get_name(ratings[0]), ratings[1])

        return recommendations[:k]

        # 正确解包预测结果
        # for pred in predictions:
        #     try:
        #         # 处理不同版本的Prediction对象
        #         if hasattr(pred, 'uid'):
        #             userID = pred.uid
        #             bookID = pred.iid
        #             estimatedRating = pred.est
        #         else:
        #             # 兼容旧版本元组格式 (uid, iid, r_ui, est, details)
        #             userID, bookID, _, estimatedRating, _ = pred
        #
        #         intBookID = int(bookID)
        #         recommendations.append((intBookID, estimatedRating))
        #     except Exception as e:
        #         print(f"处理预测时发生错误: {str(e)}")
        #         continue
        #
        # recommendations.sort(key=lambda x: x[1], reverse=True)
        #
        # for ratings in recommendations[:k]:
        #     # print(ratings)
        #     print(ml.getBookName(ratings[0]), ratings[1])

        # for algo in self.algorithms:
        #     print("\nUsing recommender ", algo.GetName())
        #
        #     print("\nBuilding recommendation model...")
        #     trainSet = self.dataset.GetFullTrainSet()
        #     algo.GetAlgorithm().fit(trainSet)
        #
        #     print("Computing recommendations...")
        #     # 用户尚未评分的电影集 AntiTestSet
        #     testSet = self.dataset.GetAntiTestSetForUser(testSubject)
        #
        #     # 对未看过的电影进行评分预测
        #     predictions = algo.GetAlgorithm().test(testSet)
        #
        #     recommendations = []
        #     print("\nWe recommend:")
        #     for userID, bookID, actualRating, estimatedRating,_ in predictions:
        #         # print(bookID)
        #         intBookID = int(bookID)
        #         recommendations.append((intBookID, estimatedRating))
        #
        #     recommendations.sort(key=lambda x: x[1], reverse=True)
        #
